/**
 * Copyright 2013 Ernesto Angel Celis de la Fuente 
 * <developer@celisdelafuente.net>

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package net.celisdelafuente.java.Android.util;

import android.app.Activity;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.IBinder;
import android.provider.Settings;

public class LocationService extends Service {
	/*public LocationService() {
	}*/

	@Override
	public IBinder onBind(Intent intent) {
		// TODO: Return the communication channel to the service.
		throw new UnsupportedOperationException("Not yet implemented");
	}
	
	
	private LocationManager manager;
	private boolean gpsEnabled;
	private Location gpsLocation;
	private Location netLocation;
	private boolean netEnabled;
	private Activity activity;
	private Location bestLocation;
	
	// TODO Maybe these could be protected or private properties, if
	// setting values dynamically is desired.
	private static final int UPDATE_FREQ = 10; // update location every 10s
	private static final int UPDATE_RANGE = 10; // update location when raching range
	private static final int MINUTES = 1000 * 60 * 2; // 2min = 1000 * 60 * min
	
	public LocationService() {}
	
	/**
	 * Initializes Android LocationManager
	 * @param caller Activity instance
	 */
	public LocationService(Activity caller) {
		activity = caller;
		getManager();
	}
	
	/**
	 * Encapsulates the LocationManager provider settings
	 */
	public LocationManager getManager() {
		manager = (LocationManager) activity
				.getSystemService(Context.LOCATION_SERVICE);
		gpsEnabled = manager.isProviderEnabled(LocationManager.GPS_PROVIDER);
		netEnabled = manager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
		return manager;
	}
	
	/**
	 * Should set some configurations, but I'm still unsure about it belonging 
	 * here, so its marked as deprecated
	 * @deprecated
	 * @param activity caller
	 */
	protected void enableLocationSettings(Activity activity) {
        Intent settingsIntent = new Intent(Settings
        		.ACTION_LOCATION_SOURCE_SETTINGS);
        activity.startActivity(settingsIntent);
    }

	// TODO Being only two providers why not stuff them in a List???
	// and get ride o fone method here, modifying the remaining to
	// inform about N providers if needed
	/**
	 * Informs about the GPS device status
	 * @return True when GPS is enabled False otherwise
	 */
	public boolean isGpsEnabled() {
		return gpsEnabled;
	}
	
	/**
	 * Informs about the Netowork location provider starus
	 * @return True when Net provider is enabled False otherwise
	 */
	protected boolean isNetEnabled() {
		return netEnabled;
	}
	
	public Location getGpsLocation() {
		return gpsLocation;
	}

	public Location getNetLocation() {
		return netLocation;
	}

	public Location getBestLocation() {
		return bestLocation;
	}

	/**
	 * Updates location from enabled provider(s), it needs a LocationListener 
	 * parameter from the Activity to update UI or anything needed
	 * @param listener
	 * @return Location , null when providers dont't return any location. 
	 */
	public Location getLocation(LocationListener listener){
		bestLocation = null;
		if(gpsEnabled) {
			manager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 
					UPDATE_FREQ, UPDATE_RANGE, listener);
			gpsLocation = manager
					.getLastKnownLocation(LocationManager.GPS_PROVIDER);
		}
		if(netEnabled) {
			manager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 
					UPDATE_FREQ, UPDATE_RANGE, listener);
			gpsLocation = manager
					.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
		}
		// If both providers return a location check for the best value
		if(gpsLocation != null && netLocation != null) {
			bestLocation = getBetterLocation(gpsLocation, netLocation);
			return bestLocation;
		} else {
			if(gpsLocation != null) {
				return gpsLocation;
			} 
			if(netLocation != null) {
				return netLocation;
			}
		}
		return null;
	}
	
	// TODO How it fits in the LocationService class???
	/** Determines whether one Location reading is better than the current Location fix.
     * Code taken from
     * http://developer.android.com/guide/topics/location/obtaining-user-location.html
     *
     * @param newLocation  The new Location that you want to evaluate
     * @param currentBestLocation  The current Location fix, to which you want to compare the new
     *        one
     * @return The better Location object based on recency and accuracy.
     */
   protected Location getBetterLocation(Location newLocation, Location currentBestLocation) {
       if (currentBestLocation == null) {
           // A new location is always better than no location
           return newLocation;
       }

       // Check whether the new location fix is newer or older
       long timeDelta = newLocation.getTime() - currentBestLocation.getTime();
       boolean isSignificantlyNewer = timeDelta > MINUTES;
       boolean isSignificantlyOlder = timeDelta < -MINUTES;
       boolean isNewer = timeDelta > 0;

       // If it's been more than two minutes since the current location, use the new location
       // because the user has likely moved.
       if (isSignificantlyNewer) {
           return newLocation;
       // If the new location is more than two minutes older, it must be worse
       } else if (isSignificantlyOlder) {
           return currentBestLocation;
       }

       // Check whether the new location fix is more or less accurate
       int accuracyDelta = (int) (newLocation.getAccuracy() - currentBestLocation.getAccuracy());
       boolean isLessAccurate = accuracyDelta > 0;
       boolean isMoreAccurate = accuracyDelta < 0;
       boolean isSignificantlyLessAccurate = accuracyDelta > 200;

       // Check if the old and new location are from the same provider
       boolean isFromSameProvider = isSameProvider(newLocation.getProvider(),
               currentBestLocation.getProvider());

       // Determine location quality using a combination of timeliness and accuracy
       if (isMoreAccurate) {
           return newLocation;
       } else if (isNewer && !isLessAccurate) {
           return newLocation;
       } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
           return newLocation;
       }
       return currentBestLocation;
   }
   
   /** Checks whether two providers are the same */
   private boolean isSameProvider(String provider1, String provider2) {
       if (provider1 == null) {
         return provider2 == null;
       }
       return provider1.equals(provider2);
   }
}
